/**
 * 命名空间
 * 在代码量比较大的情况下， 为了避免命名空间冲突，可以将相似的函数，类、接口放置到命名空间中
 * 命名空间可以将代码包裹起来，只对外暴露需要在外部访问的对象，命名空间内通过export向外到处
 * 命名空间是内部模块，主要用于组织代码，避免命名冲突
 */
namespace mycode {
    let a: string;
    a = 'aaa'
}
export namespace zoo {
    export class Dog{
        eat() {  }
    }
}
let dog_zoo = new zoo.Dog()

/**
 * 1、数据类型
 */
// 布尔类型
let married: boolean = false
// 数字类型
let age: number = 10
// 字符串类型
let firstname: string = 'zhaomeiyang'
// 数组类型
let arr1: number[] = [1, 2, 3, 4]
let arr2: Array<number> = [1, 2, 3, 4]
// 元组类型
// 在ts的基础类型中，元组表示一个已知数量和类型的数组
let arr3:[string, number] = ['aaaa', 1]

/**
 * 元组                             数组
 * 每一项可以是不同的类型           每一项都是同一类型
 * 有预定义的长度                   没有长度限制
 * 用于表示一个固定的结构           用于表示一个列表
 */

// 枚举类型
// 事先考虑某一个变量的所有的可能的值，尽量用自然语言中的单词表示它的每一个值，比如性别、月份、星期、颜色、单位、学历
// 1、普通枚举
enum Gender {
    GIRL,
    BOY
}
console.log(Gender.GIRL)  // => 输出0
enum week {
    Monday = 1,
    Tuesday = 2
}
// 2、常数类型
const enum Colors {
    Red,
    Yellow,
    Blue
}
console.log(Colors.Red) // => 输出0

// 任意类型 any
let root: HTMLElement | null = document.getElementById('root')
root!.style.color = 'red' // !非空断言

// null和undefined
// null 空 undefined 未定义
// 它们都是其他类型的子类型，你可以把它赋值给其他类型的变量
let str1: string = null
let str2: string = undefined

// void类型
// 空的 没有
// greeting函数没有返回值
function greeting(name: string): void {
    console.log('hello')
}

// never类型
// never是其他类型的子类型，代表不会出现的值
// 在函数内部永远会抛出错误，导致函数无法正常结束
function createError(message: string): never {
    console.log(1)
    throw new Error('错误')
    console.log(2)
}

/**
 * 2、类型推论
 */
// 2.1 类型推论：指编程语言中能够自动推导出值的类型的能力，它是一些强静态类型语言中出现的特性。定义时未赋值就会推论成any类型。如果定义的时候就赋值就能利用到类型推论
// 2.2 包装对象
// javascript的类型分为两种，原始数据类型和对象类型
// 所有的原始数据类型都没有属性和方法
// 在基本类型和对象类型之间切换
// 2.3 联合类型
// 联合类型表示取值可以为多种类型中的一种
// 未赋值时联合类型上只能访问两个类型共有的属性和方法
let name1: string | number;
// 2.4 类型断言
// 类型断言可以将一个联合类型的变量，指定为一个更加具体的类型
// 不能将联合类型断言为不存在的类型
let name2: string | number;
console.log((name2 as string).length) 
console.log((name2 as number).toFixed(2))
// 2.5 字面量类型
// 可以把字符串、数字、布尔值字面量组成一个联合类型
type ZType = 1 | 'One' | true
// 2.6 字符串字面量 vs 联合类型
// 字符串字面量类型用来约束取值只能是某几个字符串中的一个，联合类型表示取值可以为多种类型中的一种
// 字符串字面量限定了使用该字面量的地方仅接受特定的值，联合类型对于值并没有限定，仅仅限定值的类型需要保持一致


/**
 * 3 函数
 */
// 3.1 函数的定义
// 可以指定参数的类型和返回值的类型
function hello1(name: string): void {
    console.log(name)
}
// 3.2 函数表达式
// type 用来定义一个类型或者类型别名
type GetUsernameFunction = (x: string, y: string) => string
let getUsername: GetUsernameFunction = function(firstName, lastName) { return firstName + lastName }
// 3.3 没有返回值
let hello2 = function(name: string): void {
    console.log(name)
    return undefined
}
// 3.4 可选参数
function print(name: string, age?: number, home?: string) {}
// 3.5 默认参数
function ajax(url: string, method: string = 'get') {}
// 3.6 剩余参数
function sum(...numbers: Array<number>) {
    return numbers.reduce((accu, item) => accu + item, 0)
}
// 3.7 函数重载 overload
function attr(val: string): void
function attr(val: number): void
function attr(val: any): void {
    if(typeof val === 'string') {

    } else if(typeof val === 'number') {

    }
}

function sum2(a: string, b: string): void
function sum2(a: number, b: number): void
function sum2(a: any, b: any) {
    return a + b
}
// 3.8 ts中写箭头函数
let delay = (ms: number) => {
    setTimeout(() => {

    }, ms)
}

/**
 * 4、类
 */
// 4.1 定义类
class Person {
    name: string = 'zhao';
    age: number;
    constructor() {
        age = 10
    }
    getName(): void {
        console.log(this.name)
    }
}

// 4.2 存取器 读取器
class Person1 {
    myname: string;
    constructor(name: string) {
        this.myname = name
    }
    get name() {
        return this.myname
    }
    set name(newVal: string) {
        this.myname = newVal.toUpperCase()
    }
}
let p1 = new Person1('zhao')
console.log(p1.name)
p1.name = 'yang'
console.log(p1.name)

// 4.3 参数属性
class Person2 {
    constructor(public name: string) {}
}

class Person3 {
    constructor(public readonly name: string) {}
}
// 4.4 继承
// 子类继承父类后子类的实例上就拥有了父类中的属性和方法
class Person4 {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
    getName() {
        return this.name
    }
    setName(newName: string) {
        this.name = newName
    }
}
class Student extends Person4 {
    stuNo: number;
    constructor(name: string, age: number, stuNo: number) {
        super(name, age)
        this.stuNo = stuNo
    }
    getNo() {
        return this.stuNo
    }
    setNo(newName: number) {
        this.stuNo = newName
    }
}
let s = new Student('zhao', 10, 100)

// 4.5 访问修饰符 protected private public
// public 自己 自己的子类 其他的类都可以访问
// protected 自己的类和子类能访问
// private 只能自己访问，子类和其他类都不能访问

// 4.6 静态属性静态方法  类本身的属性和方法
class Student1{
    static aaa = 'zs'
    static getName() {
        return this.aaa
    }
}
Student1.aaa
Student1.getName()

// 4.7 抽象类
// 抽象描述一种抽象的概念，无法被实例化，只能被继承。无法创建抽象类的实例。抽象方法不能在抽象类中实现，只能在抽象类的具体子类中实现，而且必须实现
abstract class Animal {
    name!: string;
    abstract speak(): void;
}
class Cat extends Animal {
    speak(): void {
        console.log(111)
    }
}
let cat = new Cat()
cat.speak()

// 访问控制修饰符  private protected public
// 只读属性  readonly
// 抽象类、抽象方法 abstract

// 抽象类 vs 接口
// 不同类之间公有的属性或方法，可以抽象成一个接口
// 抽象类是供其他类继承的基类，抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
// 抽象类的本质是一个无法被实例化的类。其中能够实现方法和初始化属性，而接口仅能够用于描述，既不能提供方法的实现，也不为属性进行初始化
// 一个类可以继承一个类或抽象类，但可以实现多个接口
// 抽象类也可以实现接口

// 4.8 重写和重载
// 重写是指子类重写继承父类中的方法
// 重载是指为同一个函数提供多个类型定义
class Animal1 {
    speak(): void {console.log(1)}
}
class Cat2 extends Animal1 {
    speak(): void {
        console.log(2)
        super.speak() // super可以调父类中的方法，super指的就是父类，super = Animal.prototype
    }
}


/**
 * 5、接口
 */
// 可以用来描述对象的，指的是对象有哪些属性，属性是什么类型
// 接口就是把一些类中共有的属性和方法抽象出来，可以用来约束实现此接口的类
// 一个类可以继承另一个类并实现多个接口
// 接口像插件一样是用来增强类的，而抽象类是具体类的抽象概念
// 一个类可以实现多个接口，一个接口也可以被多个类实现，但一个类可以有多个子类，但只能有一个父类
// 接口里不能放实现，只能放定义

// 5.1 定义接口
// interface中可以用分号或者都好分割每一项，也可以什么都不加 
interface Point {
    x: string,
    y: string
}
let point: Point = {
    x: '1',
    y: '2'
}
// 可以用来描述行为的抽象
interface Speakable {
    speak(): void
}
interface Eatable {
    eat(): void
}
// 类可以实现多个接口，但只能继承一个父类
class Person5 implements Speakable, Eatable {
    // 实现speak eat
    speak(): void {
        
    }
    eat(): void {
        
    }
}
// 5.2 任意属性
interface Person {
    readonly id: number
    name: string
    [propName: string]: any // 任意属性
}
let p2 = {
    id: 1,
    name: 'zhao',
    age: 10
}
// 5.3 接口的继承
interface Speakable {
    speak(): void
}
interface SpeakChinese extends Speakable {
    speakChinese(): void
}
class Person6 implements SpeakChinese {
    speak() {}
    speakChinese(): void {}
}

// 5.4 接口的只读 readonly
interface Circle {
    readonly PI: number
    radius: number
}
let circle:Circle = {
    PI: 3.14,
    radius: 1
}

// 5.5 接口用来约束函数
interface Discount {
    (price: number): number
}
let cost: Discount = function (price: number): number {
    return price * 0.8
}

// 5.6 可索引接口
// 用来对数组和对象进行约束
interface UserInterface {
    [index: number]: string
}
let arr: UserInterface = ['a', 'b', 'c']
let obj2: UserInterface = {
    1: '1',
    2: '2'
}
// 5.7 类接口 可以用接口装饰类
interface Speakable1 {
    name: string
    speak(words: string): void
}
class Dog implements Speakable1 {
    name: string;
    speak() {}
}

// 5.8 约束构造函数
// 使用new约束构造函数
interface WithNameClass {
    new(name: string): Animal2
}
class Animal2 {
    constructor(public name: string) { }
}
function createAnimal(clazz: WithNameClass, name: string) {
    return new clazz(name)
}
let a2 = createAnimal(Animal2, 'zhao')


/**
 * 6、泛型
 * 在定义函数、接口、或类的时候，不预先指定具体的类型，而是在使用额时候再指定类型的一种特性
 * 泛型T作用域仅限于函数内部使用
 */
// 为什么会有泛型
// 
// 7.1 泛型函数
function createArray<T>(length: number, val: T): Array<T> {
    let res: Array<T> = []
    for(let i = 0; i < length; i++) {
        res[i] = val
    }
    return res
}
let result = createArray<string>(3, 'a')

// 7.2 泛型类
class MyArray<T> {
    private list: T[] = [];
    add(val: T) {
        this.list.push(val)
    }
    getMax(): T {
        let res = this.list[0]
        for(let i = 0; i < this.list.length; i++) {
            if(this.list[i] > res) {
                res = this.list[i]
            }
        }
        return res
    }
}
let arr4 = new MyArray<number>()
arr4.add(1)
let result2: number = arr4.getMax()

// 7.3 接口泛型
interface Calculate {
    <T>(a: T, b: T): T
}
let add1: Calculate = function<T>(a: T, b: T): T {
    return a
}
add1<number>(1, 2)

// 7.4 多个类型参数
function swap<A, B>(tuple: [A, B]): [B, A] {
    return [tuple[1], tuple[0]]
}
let result3 = swap<string, number>(['zhao', 10])

// 7.5 默认泛型类型
function createArray2<T=number>(length: number, val: T): Array<T> {
    let res: Array<T> = []
    for(let i = 0; i < length; i++) {
        res[i] = val
    }
    return res
}
let result4 = createArray2(3, 'a')

// 7.6 泛型约束
// 在函数中使用泛型的时候，由于预先不知道具体的类型，所有不能访问相应类型的方法
interface lengthWise {
    length: number
}
function logger<T extends lengthWise>(val: T) {
    console.log(val.length)
}

interface Cart<T> {
    list: T[]
}
let cart: Cart<string> = {
    list: ['1', '2']
}

// 7.7 泛型类型别名
type Cart2<T> = { list: T[] } | T[]
let c1: Cart2<string> = {
    list: ['1']
}
let c2: Cart2<string> = ['2']

// interface 定义一个实实在在的接口，它是一个真正的类型
// type 一般用来定义别名，并不是真正的类型
/**
 * 接口创建了一个新的名字，它可以在其他任意地方被调用。而类型别名并不创建新的名字
 * 类型别名不能被extends和implements，这时我们应该尽量使用接口代替类型别名
 * 当我们需要使用联合类型或者元组类型的时候，类型别名会更合适
 */


/**
 * 8、结构类型系统
 */
// 8.1 接口的兼容性
// 如果传入的变量和声明的类型不匹配，ts就会进行兼容性检查
// 原来是duck-check，就是说只要目标类型中声明的属性变量在源类型中都存在就是兼容的

interface Animal8 {
    name: string
    age: number
}
interface Person8 {
    name: string
    age: number
    speak: (words: string) => void
}
function getName1(animal: Animal8): string {
    return animal.name
}
let p4: Person8 = {
    name: 'zhao',
    age: 20,
    speak() {}
}
console.log(getName1(p4)) // 正常输出
// 接口的兼容性： 在ts中，只跟属性有关系，我要的你有就可以正常执行代码

// 8.2 基本类型的兼容性
let num1: string | number
let str: string = 'hello'
num1 = str

let num2 : {
    toString(): string
}
let str3: string = 'zhao'
num2 = str3

// 8.3 类的兼容性
// 类的兼容性和类型无关，只要属性有就可以
namespace b {
    class Animal {
        name: string
    }
    class Bird extends Animal {
        swing: number
    }
    let a : Animal
    a = new Bird() // 父类的变量能指向子类的实例

    let b: Bird
    // b = new Animal() 把swing属性注释调就不会报错
}

// 8.4 函数的兼容性
namespace c {
    // 比较参数
    type sumFunction = (a: number, b: number) => number
    let sum: sumFunction
    function f1(a: number, b: number): number {
        return a
    }
    sum = f1
    function f2(a: number): number {
        return a
    }
    sum = f2
    function f4(a: number, b: number, c: number): number {
        return a
    }
    // sum = f4 参数可以少，但是不能多

    // 比较返回值
    type GetPerson = () => { name: string, age: number }
    let getPerson: GetPerson
    function g1() {
        return { name: 'stirng', age: 10 }
    }
    getPerson = g1
    function g2() {
        return { name: 'string' }
    }
    // getPerson = g2 少了不行，多了可以

    // 函数参数的协变
    type logFunc = (a: number | string) => void
    let log: logFunc
    function log1(a: number | string | boolean) {
        console.log(1)
    }
    log = log1 // 参数类型可以多，不能少
}

// 8.5 泛型的兼容性
// 判断兼容性的时候先判断具体的类型，再进行兼容性判断
namespace d {
    interface Empty<T> {

    }
    let x!: Empty<string>
    let y!: Empty<number>
    x = y // 如果接口里边有属性了，就不能赋值了

    interface NotEmpty<T> {
        data: string
    }
}

// 8.6 枚举的兼容性
// 枚举类型与数字类型兼容，并且数字类型与枚举类型兼容
// 不同枚举类型之间是不兼容的
namespace e {
    enum Colors {
        Red,
        Yellow
    }
    let c: Colors
    c = Colors.Red // c的值本来就是0
    c = 1
}


/**
 * 类型保护
 */
// 类型保护就是一些表达式，他们在编译的时候就能通过类型信息确保某个作用域内变量的类型
// 类型保护就是能够通过关键字判断出分支中的类型
// 类型保护就是更精确的知道是哪种类型
// 9.1 typeof
namespace  f {
    // 9.1 typeof
    function double(input: string | number | boolean) {
        if(typeof input === 'string') {

        } else if(typeof input === 'number') {

        } else {

        }
    }

    // 9.2 instance of
    class Animal {
        public name: string = 'zhao'
    }
    class Bird extends Animal {
        public swing: number = 2
    }
    function getName(a: Animal) {
        if(a instanceof Bird) {
            a.swing
        } else {
            a.name
        }
    }

    // 9.3 null保护
    function getFirstLetter(s: string | null) {
        if(s === null) {
            s = ''
        }
        return s.charAt(0)
    }

    // 9.4 链式判断运算符 ?.
    // 9.5 可辨识的联合类型
    // 就是利用联合类型中的共有字段进行类型保护的一种技巧
    // 相同字段的不同取值就是可辨识
    interface warningButton {
        class: 'warning',
        text1: '修改'
    }
    interface dangerButton {
        class: 'danger',
        text2: '删除'
    }
    type button = warningButton | dangerButton
    function getButton(button: button) {
        if(button.class === 'warning') {

        } else {

        }
    }

    interface Bird {
        swing: number
    }
    interface Dog {
        leg: number
    }
    function getNumber(x: Bird | Dog) {
        if('swing' in x) {

        } else {

        }
    }
}

namespace g {
    // 自定义类型保护
    interface Bird {
        name1: '鸟'
        leg: number
    }
    interface Dog {
        name2: '狗'
        leg: number
    }
    function isBird(x: Bird | Dog): x is Bird {
        return x.leg === 2
    }
    function getAnimal(x: Bird | Dog) {
        if(isBird(x)) {
            x.name1
        } else {
            x.name2
        }
    }
}

/**
 * 类型变换
 */
// 10.1 交叉类型
// 交叉类型表示将多个类型合并为一个类型
namespace h {
    interface Bird {
        name: string
        fly(): void
    }
    interface Person {
        name: string
        eat(): void
    }
    type BirdMan = Bird & Person
    let p: BirdMan = {
        name: 'zhao',
        fly() {},
        eat() {}
    }

    // typeof 可以获取一个变量的类型
    type Person1 = {
        name: string
        age: number
    }
    let p1 = {
        name: 'zhao',
        age: 20
    }
    // type 用来定义类型，let定义类型
    type Person2 = typeof p1
    let p2: Person2 = {
        name: 'yang',
        age: 20
    }

    // 索引访问操作符
    // 可以通过[]来获取一个类型的子类型
    interface Person3 {
        name: String
        age: number
        job: {
            name: string
        }
        interests: {name: string, level: number}[]
    }
    let myjob: Person3['job'] = {
        name: 'aaa'
    }
    let mylevel: Person3['interests'][0]['level'] = 10

    // keyof 索引类型查询操作符
    interface Person4 {
        name: string
        age: number
        gender: 'male' | 'female'
    }
    type Person3Key = keyof Person3
    function getValueByKey(val: Person4, key: Person3Key) {

    }

    // 映射类型 在定义的时候用in操作符去批量定义
    interface Person5 {
        name: string
        age: number
        gender: 'male' | 'female'
    }
    type PartialPerson = {
        [key in keyof Person5]?: Person5[key]
    }
    

    // 内置工具类型
    // ts中内置了一些工具类型来帮助我们更好的使用类型系统

    // partial 可以将传入的属性由非可选变成可选
    type Partial<T> = { [ p in keyof T ]?: T[p] } // 内置Partial的实现原理
    type PartialPerson1 = Partial<Person5>
    // 必填的
    type Required<T> = {
        [key in keyof T]-?: T[key]  // 减号表示必选
    } // Required的实现原理
    type RequiredPerson = Required<Person5>
    // 只读的
    type Readonly<T> = {
        readonly [key in keyof T]: T[key]
    } // Readonly的实现原理
    type ReadOnlyPerson = Readonly<Person5>
    // 选择某一个或者某些属性
    type Pick<T, K extends keyof T> = {
        [key in K]: T[key]
    } // pick的实现原理
    type pickperson = Pick<Person5, 'name'>


    // 条件类型
    // 在定义泛型的时候能够添加逻辑分支，让泛型更加灵活
    interface Fish {
        name1: string
    }
    interface Bird {
        name2: string
    }
    interface Water {
        name3: string
    }
    interface Sky {
        name4: string
    }
    type Condition<T> = T extends Fish ? Water : Sky
    let condition: Condition<Fish> = {
        name3: 'water'
    }

    // 条件类型的分发
    type Condition2<T> = T extends Fish ? Water : Sky
    // Condition2<Fish | Bird> = Water | Sky
    let c1: Condition2<Fish | Bird> = { name3: 'aa' }
    let c2: Condition2<Fish | Bird> = { name4: 'bbb' }
}

namespace i {
    // 内置条件类型
    // Exclude
    // 从T中排除U
    type E = Exclude<string|number, string>
    let e: E = 10

    // Extract
    // 从T可分配的类型中提取U
    type E1 = Extract<string|number, string>
    let e1: E1 = '1'

    // NonNullable
    // 拿不为空的
    type E3 = NonNullable<string|number|null|undefined>
    let e3: E3 = 'hello'

    // ReturnType
    // 返回值类型
    function getUserInfo() {
        return {
            name: 'zhao',
            age: 10
        }
    }
    type UserInfo = ReturnType<typeof getUserInfo>
    let user: UserInfo = {
        name: 'yang',
        age: 2
    }

    // instanceType 获取构造函数的实例类型
    class Person {
        name: string
        constructor(name: string) {
            this.name = name
        }
    }
    type P = InstanceType<typeof Person>
    let p: P = new Person('zhao')
}


/**
 * 装饰器
 * 装饰器是一种特殊类型的声明，它能够被附加到类声明、方法、属性或者参数上，可以修改类的行为
 * 常见的装饰器有类装饰器、属性装饰器、方法装饰器和参数装饰器
 * 装饰器的写法分为普通装饰器和装饰器工厂
 */
namespace de {
    // 类装饰器
    // 类装饰器在类声明之前，用来监视、修改或替换类定义
    function enhancer(target: any) {
        // target代表当前类
        // 往类上挂载属性
        // target.name
        // 往实例上
    }
    @enhancer
    class Person {
        constructor() {

        }
    }
    let p = new Person()
    console.log(p.name)


    // 装饰器工厂
    function enhancer1(clazz: any) {
        return function enhancer(target: any) {
            return class Child extends target {

            }
        }
    }
    @enhancer1(2)
    class Person1 {

    }
}





